﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace Bricks.Data
{

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class Repository<TEntity> : FacadeBase<IContextFacade>, IRepository<TEntity> where TEntity : class
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contextFacade"></param>
        public Repository(IContextFacade contextFacade) :
            base(() => contextFacade)
        {
            if (contextFacade == null)
            {
                throw new ArgumentNullException("contextFacade");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="contextFacadeAccessor"></param>
        public Repository(Func<IContextFacade> contextFacadeAccessor) :
            base(contextFacadeAccessor) { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public static void Transact(Action<IRepository<TEntity>> action)
        {
            Run(repository => repository.Transact(() => action.Invoke(repository)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="action"></param>
        public static void Transact(string key, Action<IRepository<TEntity>> action)
        {
            Run(key, repository => repository.Transact(() => action.Invoke(repository)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Transact<TResult>(Func<IRepository<TEntity>, TResult> action)
        {
            return Run<TResult>(repository => repository.Transact<TResult>(() => action.Invoke(repository)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="key"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Transact<TResult>(string key, Func<IRepository<TEntity>, TResult> action)
        {
            return Run<TResult>(key, repository => repository.Transact<TResult>(() => action.Invoke(repository)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public static void Run(Action<IRepository<TEntity>> action)
        {
            Run<bool>(
                repository =>
                {
                    action.Invoke(repository);

                    return true;
                });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="key"></param>
        /// <param name="action"></param>
        public static void Run(string key, Action<IRepository<TEntity>> action)
        {
            Run<bool>(key,
                repository =>
                {
                    action.Invoke(repository);

                    return true;
                });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Run<TResult>(Func<IRepository<TEntity>, TResult> action)
        {
            return UnitOfWork.Run<TResult>((Func<IContextFacade> contextFacadeAccessor) => action.Invoke(new Repository<TEntity>(contextFacadeAccessor)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="key"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public static TResult Run<TResult>(string key, Func<IRepository<TEntity>, TResult> action)
        {
            return UnitOfWork.Run<TResult>(key, (Func<IContextFacade> contextFacadeAccessor) => action.Invoke(new Repository<TEntity>(contextFacadeAccessor)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="action"></param>
        public void Transact(Action action)
        {
            Execute(contextFacade => contextFacade.Transact(action));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="action"></param>
        /// <returns></returns>
        public TResult Transact<TResult>(Func<TResult> action)
        {
            return Execute<TResult>(contextFacade => contextFacade.Transact(action));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public bool Contains(Expression<Func<TEntity, bool>> predicate)
        {
            return Execute<bool>(contextFacade => contextFacade.Contains<TEntity>(predicate));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public int Count(Expression<Func<TEntity, bool>> predicate)
        {
            return Execute<int>(contextFacade => contextFacade.Count<TEntity>(predicate));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public int CountAll()
        {
            return Execute<int>(contextFacade => contextFacade.CountAll<TEntity>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public IEnumerable<TEntity> Query(Expression<Func<TEntity, bool>> predicate)
        {
            return Execute<IEnumerable<TEntity>>(contextFacade => contextFacade.Query<TEntity>(predicate));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public PagedResult<TEntity> Query(Expression<Func<TEntity, bool>> predicate, int pageIndex, int pageSize)
        {
            return Execute<PagedResult<TEntity>>(contextFacade => contextFacade.Query<TEntity>(predicate, pageIndex, pageSize));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerable<TEntity> QueryAll()
        {
            return Execute<IEnumerable<TEntity>>(contextFacade => contextFacade.QueryAll<TEntity>());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public PagedResult<TEntity> QueryAll(int pageIndex, int pageSize)
        {
            return Execute<PagedResult<TEntity>>(contextFacade => contextFacade.QueryAll<TEntity>(pageIndex, pageSize));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public TEntity QuerySingle(Expression<Func<TEntity, bool>> predicate)
        {
            return Execute<TEntity>(contextFacade => contextFacade.QuerySingle<TEntity>(predicate));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        public void Add(TEntity entity)
        {
            Execute(contextFacade => contextFacade.Add<TEntity>(entity));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        public void Remove(TEntity entity)
        {
            Execute(contextFacade => contextFacade.Remove<TEntity>(entity));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        public void Update(TEntity entity)
        {
            Execute(contextFacade => contextFacade.Update<TEntity>(entity));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        public void Merge(TEntity entity)
        {
            Execute(contextFacade => contextFacade.Merge<TEntity>(entity));
        }
    }
}
